package icu.zlz.emqx.service.impl;

import com.alibaba.nacos.shaded.com.google.gson.JsonObject;
import icu.zlz.common.core.utils.StringUtils;
import icu.zlz.common.core.web.domain.AjaxResult;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.Optional;

/**
 * @author zangsheng
 */
@Service
public class EmqxService {
    @Value("${emqx.url}")
    private String baseUrl;
    @Value("${emqx.apiKey}")
    private String apiKey;
    @Value("${emqx.secretKey}")
    private String secretKey;


    private static final MediaType MEDIA_TYPE_JSON = MediaType.get("application/json; charset=utf-8");
    private final OkHttpClient client = new OkHttpClient();


    private Request.Builder getBuilder(String url) {
        Request.Builder builder = new Request.Builder();
        builder.url(baseUrl + url);
        builder.header("Content-Type", "application/json");
        builder.header("Authorization", Credentials.basic(apiKey, secretKey));
        return builder;

    }

    private AjaxResult getResponse(Request.Builder builder) {
        try {
            try (Response response = client.newCall(builder.build()).execute()) {
                if (response.body() != null) {
                    if (!response.isSuccessful()) {
                        return AjaxResult.error(response.code(), "HTTP request failed with  msg: " + response.body().toString());
                    }
                    return AjaxResult.success("success", response.body().string());
                } else {
                    return AjaxResult.error("Response body is null");
                }
            }

        } catch (IOException e) {
            return AjaxResult.error(e.getMessage());
        }
    }


    /**
     * QoS 0：至多一次（At most once）
     * 消息发布完全依赖底层TCP/IP网络。
     * 可能会发生消息丢失或重复。
     * 适用于对消息丢失不敏感的场景，如环境传感器数据，因为不久之后通常会有新的数据发送。
     * QoS 1：至少一次（At least once）
     * 确保消息能够到达接收方。
     * 但可能会发生消息重复。
     * 适用于需要确保消息到达但可以接受重复消息的场景，如计费系统，其中消息重复或丢失可能导致不正确的结果。
     * QoS 2：仅一次（Exactly once）
     * 确保消息只被接收方接收一次。
     * 订阅方会临时存储消息，并与服务端确认只收到一次后才推给上层应用处理。
     * 是最严格的QoS等级，适用于对消息传递有严格要求的场景。
     *
     * @param clientid
     * @param topic
     * @param qos
     * @param nl       (MQTT5协议生效)该选项用于控制是否接收由同一客户端发布的消息。当设置为1时，表示不接收；当设置为0时，表示接收。
     * @param rap      (MQTT5协议生效)该选项用于控制是否保留消息为已发布状态。当设置为1时，表示保留；当设置为0时，表示不保留。
     * @param rh       (MQTT5协议生效)该选项用于控制如何处理保留消息。它有三个可能的值：0：表示按照MQTT V3.1.1的保留消息处理规则。1：表示在订阅时接收最新的保留消息，并在之后忽略所有保留消息的更新。2：表示在订阅时接收最新的保留消息，并在之后接收所有保留消息的更新。
     * @return
     */
    public AjaxResult clientSubscribe(String clientid, String topic, Integer qos, Integer nl, Integer rap, Integer rh) {
        String url = "/clients/" + clientid + "/subscribe";
        if (StringUtils.isEmpty(topic)) {
            throw new IllegalArgumentException("topic不能为空");
        }

        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("topic", topic);
        jsonObject.addProperty("qos", ObjectUtils.isEmpty(qos) || qos > 2 || qos < 0 ? 0 : qos);
        jsonObject.addProperty("nl", ObjectUtils.isEmpty(nl) || nl > 1 || nl < 0 ? 0 : nl);
        jsonObject.addProperty("rap", ObjectUtils.isEmpty(rap) || rap > 1 || rap < 0 ? 0 : rap);
        jsonObject.addProperty("rh", ObjectUtils.isEmpty(rh) || rh > 2 || rh < 0 ? 0 : rh);

        Request.Builder builder = getBuilder(url);
        builder.post(RequestBody.create(jsonObject.toString(), MEDIA_TYPE_JSON));
        return getResponse(builder);

    }
    //客户端 ID，可以通过重复参数指定多个值：clientid=c1&clientid=c2


    /**
     * 查询客户端信息
     *
     * @param page     页数 默认1
     * @param limit    每页的条数 默认10
     * @param username 用户名 eg: ['user1','user2','user3']
     * @param clientid 用户id eg: ['clientid1','clientid2','clientid3']
     * @param node     emqx节点 eg: emqx@127.0.0.1
     * @return
     */
    public AjaxResult getClients(Integer page, Integer limit, String[] username, String[] clientid, String node) {
        int validatedPage = validatePage(page);
        int validatedLimit = validateLimit(limit);
        StringBuilder url = new StringBuilder("/clients");
        appendParamIfNotNullOrEmpty(url, "page", new String[]{String.valueOf(validatedPage)});
        appendParamIfNotNullOrEmpty(url, "limit", new String[]{String.valueOf(validatedLimit)});
        appendParamIfNotNullOrEmpty(url, "username", username);
        appendParamIfNotNullOrEmpty(url, "clientid", clientid);
        appendParamIfNotNullOrEmpty(url, "node", new String[]{node});

        Request.Builder builder = getBuilder(String.valueOf(url));
        builder.get();
        return getResponse(builder);


    }

    private int validatePage(Integer page) {
        return Optional.ofNullable(page).orElse(1);
    }

    private int validateLimit(Integer limit) {
        return Optional.ofNullable(limit).map(l -> Math.min(l, 10000)).orElse(10);
    }

    private void appendParamIfNotNullOrEmpty(StringBuilder url, String paramName, String[] values) {

        if (!ObjectUtils.isEmpty(values)) {
            for (String value : values) {
                if (StringUtils.hasText(value)) {
                    if (url.chars().anyMatch(c -> c == '?')) {
                        url.append("&").append(paramName).append("=").append(value);
                    } else {
                        url.append("?").append(paramName).append("=").append(value);
                    }
                }
            }
        }
    }
}
